/*
 * This file is part of JFlowMap.
 *
 * Copyright 2009 Ilya Boyandin
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package jflowmap.views.flowmap;


import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.util.Comparator;

import jflowmap.model.graph.Node;
import jflowmap.model.ontology.Dimension;
import jflowmap.model.ontology.SelectionListener;
import jflowmap.util.Exportable;
import jflowmap.util.geom.GeomUtils;
import jflowmap.util.geom.Point;
import jflowmap.util.piccolo.PNodes;
import jflowmap.views.ColorCodes;
import jflowmap.views.Disposable;

import org.apache.log4j.Logger;

import com.google.common.base.Function;

import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PCanvas;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.event.PBasicInputEventHandler;
import edu.umd.cs.piccolo.event.PInputEvent;
import edu.umd.cs.piccolo.event.PInputEventListener;
import edu.umd.cs.piccolox.util.PFixedWidthStroke;


/**
 * @author Ilya Boyandin
 */
public class VisualNode extends PNode implements Exportable, SelectionListener, Disposable {
	private static final long serialVersionUID = 1L;
	private static Logger logger = Logger.getLogger(VisualNode.class);

	public enum Attributes {
		SELECTED, HIGHLIGHTED;
	}

	public static final Stroke STROKE = new PFixedWidthStroke(1);
	public static final Stroke HIGHLIGHTED_STROKE = new PFixedWidthStroke(1);
	public static final Stroke SELECTED_STROKE = new PFixedWidthStroke(2);

	private final VisualFlowMap visualFlowMap;
	private final Node node;

	private final double origX;
	private final double origY;

	private VisualNodeDrawingStrategy drawingStrategy;

	public VisualNode(VisualFlowMap visualFlowMap, Node node, double origX, double origY) {
		if (Double.isNaN(origX)  ||  Double.isNaN(origY)) {
			logger.error("NaN coordinates passed in for node: " + node);
			throw new IllegalArgumentException("NaN coordinates passed in for node " + node);
		}
		this.origX = origX;
		this.origY = origY;
		this.node = node;

		this.visualFlowMap = visualFlowMap;
		addInputEventListener(INPUT_EVENT_HANDLER);

		visualFlowMap.getAbstractView().getView()
		.getSelection("standard").addSelectionListener(node.getDimension(), this);

		updateSize();
		if (visualFlowMap.getAbstractView().getView()
				.getSelection("standard").isSelected("TARGET", node.getDimension())) {
			this.select("TARGET", node.getDimension());
		}
	}

	public final Point2D getPoint() {
		return new Point2D.Double(origX, origY);
	}

	protected final void updatePositionInCamera(PCamera cam) {
		Point2D p = getPoint();
		setVisible(cam.getViewBounds().contains(p));

		cam.viewToLocal(p);
		PNodes.setPosition(this, p.getX(), p.getY(), true);
	}

	protected final void updateSize() {
		this.updateSize(false);
	}

	protected final void updateSize(boolean resetPosition) {
		if (drawingStrategy != null) {
			removeChild(drawingStrategy);
		}

		if (node.getIncomingEdges().size() > 0) {
			VisualNodeDrawingStrategy oldStrategy = null;
			if (!resetPosition && drawingStrategy instanceof PieChartDrawingStrategy) {
				oldStrategy = drawingStrategy;
			}  

			if (visualFlowMap.getShowOnlySelected() && !this.isSelected()) {
				drawingStrategy = new CircleDrawingStrategy(getX(), getY(), this);
				this.moveToBack();

			} else {
				drawingStrategy = PieChartDrawingStrategy.create(getX(), getY(), this,
						visualFlowMap.getPieDimension(), oldStrategy);
			} 

		} else {
			drawingStrategy = new NullDrawingStrategy(this);
		}

		drawingStrategy.setPlacer(visualFlowMap.getDynamicPlacer());
		drawingStrategy.setStroke(STROKE);
		drawingStrategy.setPaint(visualFlowMap.getColor(ColorCodes.NODE_PAINT));

		addChild(drawingStrategy);

		drawingStrategy.moveToBack();
		updateColorsAndStroke();
	}

	public final double getValueX() {
		return origX;
	}

	public final double getValueY() {
		return origY;
	}

	public final Node getNode() {
		return node;
	}

	public String getLabel() {
		StringBuilder sb = new StringBuilder();
		sb.append(node.getName());		
		return sb.toString();
	}

	private static final PInputEventListener INPUT_EVENT_HANDLER = new PBasicInputEventHandler() {
		@Override
		public void mouseClicked(PInputEvent event) {
			VisualNode vnode = PNodes.getAncestorOfType(event.getPickedNode(), VisualNode.class);
			if (vnode == null) return;

			if (event.getClickCount() == 1) {
				if (vnode.isSelected()) {
					vnode.moveToFront();
					vnode.getVisualFlowMap().getAbstractView().getView()
					.getSelection("standard").unselect("TARGET", vnode.getNode().getDimension());
				} else {
					vnode.getVisualFlowMap().getAbstractView().getView()
					.getSelection("standard").select("TARGET", vnode.getNode().getDimension());
					vnode.moveToBack();
				}

				if (vnode.visualFlowMap.getShowOnlySelected()) {
					vnode.visualFlowMap.updateNodeSizes(true);
				}

			} else if (event.getClickCount() == 2) {
				/*if (commentWindow != null)
    			commentWindow.dispose();
    		commentWindow = new CommentWindow("Anmerkungen", vnode.getLabel() + " - " + vnode.visualFlowMap.getFlowWeightAttr(),
    				vnode.visualFlowMap.getFlowMapGraph()
    				.getCommentsOn(vnode.node, vnode.visualFlowMap.getFlowWeightAttr()),
    				vnode);

    		commentWindow.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    		commentWindow.setBackground(Color.WHITE);
    		commentWindow.pack();
    		commentWindow.setVisible(true);*/
			}
		}

		@Override
		public void mouseEntered(PInputEvent event) {
			VisualNode vnode = PNodes.getAncestorOfType(event.getPickedNode(), VisualNode.class);
			if (vnode == null) return;
			vnode.setHighlighted(true);
			vnode.getVisualFlowMap().showTooltip(vnode, event.getPosition());
		}

		@Override
		public void mouseExited(PInputEvent event) {
			VisualNode vnode = PNodes.getAncestorOfType(event.getPickedNode(), VisualNode.class);
			if (vnode == null) return;
			vnode.setHighlighted(false);
			vnode.getVisualFlowMap().hideTooltip();
		}
	};


	public boolean isSelected() {
		return getBooleanAttribute(Attributes.SELECTED.name(), false);
	}

	private void setSelected(boolean selected) {
		addAttribute(Attributes.SELECTED.name(), selected);
		if (selected == false) {
			this.setHighlighted(false);
			this.moveToBack();

		} else {
			this.moveToFront();
		}

		updateColorsAndStroke();
		updateLegend();
	}

	public boolean isHighlighted() {
		return getBooleanAttribute(Attributes.HIGHLIGHTED.name(), false);
	}

	public void setHighlighted(boolean highlighted) {
		addAttribute(Attributes.HIGHLIGHTED.name(), highlighted);
		updateColorsAndStroke();
	}

	void updateColorsAndStroke() {
		boolean selected = isSelected();
		boolean highlighted = isHighlighted();
		if (selected) {
			drawingStrategy.setStroke(SELECTED_STROKE);
			drawingStrategy.setStrokePaint(visualFlowMap.getColor(ColorCodes.NODE_SELECTED_STROKE_PAINT));
			drawingStrategy.setPaint(visualFlowMap.getColor(ColorCodes.NODE_SELECTED_PAINT));
			drawingStrategy.showLegend();

		} else if (highlighted) {
			drawingStrategy.setStroke(SELECTED_STROKE);
			drawingStrategy.setStrokePaint(visualFlowMap.getColor(ColorCodes.NODE_STROKE_PAINT));
			drawingStrategy.setPaint(visualFlowMap.getColor(ColorCodes.NODE_HIGHLIGHTED_PAINT));

		} else {
			drawingStrategy.setStroke(STROKE);
			drawingStrategy.setStrokePaint(visualFlowMap.getColor(ColorCodes.NODE_STROKE_PAINT));
			drawingStrategy.setPaint(visualFlowMap.getColor(ColorCodes.NODE_PAINT));
		}
	}

	private void updateLegend() {
		if (isSelected()) {
			drawingStrategy.showLegend();
		} else {
			drawingStrategy.hideLegend();
		}
	}


	@Override
	public String toString() {
		return "VisualNode{" +
				"label=" + getLabel() +
				'}';
	}

	public double distanceTo(VisualNode node) {
		return GeomUtils.distance(getValueX(), getValueY(), node.getValueX(), node.getValueY());
	}

	public boolean hasIncomingEdges() {
		return node.getIncomingEdges().size() > 0;
	}

	public boolean hasEdges() {
		return hasIncomingEdges();
	}

	public static final Comparator<VisualNode> LABEL_COMPARATOR = new Comparator<VisualNode>() {
		public int compare(VisualNode o1, VisualNode o2) {
			return o1.getLabel().compareTo(o2.getLabel());
		}
	};

	public static final Function<VisualNode, Point> TRANSFORM_NODE_TO_POSITION = new Function<VisualNode, Point>() {
		public Point apply(VisualNode node) {
			return new Point(node.getX(), node.getY());
		}
	};

	@Override
	public PCanvas startExport() {
		return drawingStrategy.startExport();
	}

	@Override
	public void endExport() {
		drawingStrategy.endExport();  
	}

	@Override
	public String getExportName() {
		return drawingStrategy.getExportName() + " - " + visualFlowMap.getGraph().getName();
	}

	@Override
	public String getData() {
		StringBuilder builder = new StringBuilder();

		/*for (VisualEdge ve: getEdgesOfTargetType(visualFlowMap.getTargetType())) {
		  final double weight = ve.getEdgeWeight();
		  if (!Double.isNaN(weight)) {
			  builder.append(ve.getOppositeNode(this).getLabel());
			  builder.append(": " + ve.getEdgeWeight());
			  builder.append(" (" + visualFlowMap.getTargetType());
			  builder.append(")<p>");
		  }
	  }*/

		return builder.toString();
	}

	@Override
	public PCanvas exportLegend() {
		return drawingStrategy.exportLegend();
	}

	public VisualNodeDrawingStrategy getDrawingStrategy () {
		return drawingStrategy;
	}

	public final VisualFlowMap getVisualFlowMap () {
		return visualFlowMap;
	}

	@Override
	public void select(String category, Dimension dimension) {
		if ("TARGET".equals(category)) {
			this.setSelected(true);
		}
	}

	@Override
	public void unselect(String category, Dimension dimension) {
		if ("TARGET".equals(category)) {
			this.setSelected(false);
		}
	}

	@Override
	public void dispose() {
		visualFlowMap.getAbstractView().getView()
		.getSelection("standard").removeSelectionListener(node.getDimension(), this);
	}
}
